{
 "cells": [
  {
   "cell_type": "markdown",
   "id": "a0e0e30e",
   "metadata": {},
   "source": [
    "# Cross Encoder Reranker\n",
    "\n",
    "## 개요\n",
    "\n",
    "Cross encoder reranker는 검색 증강 생성(RAG) 시스템의 성능을 향상시키기 위해 사용되는 기술입니다. 이 문서는 Hugging Face의 cross encoder 모델을 사용하여 retriever에서 reranker를 구현하는 방법을 설명합니다.\n",
    "\n",
    "## 주요 특징 및 작동 방식\n",
    "\n",
    "1. **목적**: 검색된 문서들의 순위를 재조정하여 질문에 가장 관련성 높은 문서를 상위로 올림\n",
    "2. **구조**: 질문과 문서를 동시에 입력으로 받아 처리\n",
    "3. **작동 방식**:\n",
    "  - 질문과 문서를 하나의 입력으로 사용하여 유사도를 직접 출력\n",
    "  - Self-attention 메커니즘을 통해 질문과 문서를 동시에 분석\n",
    "4. **장점**:\n",
    "  - 더 정확한 유사도 측정 가능\n",
    "  - 질문과 문서 사이의 의미론적 유사성을 깊이 탐색\n",
    "5. **한계점**:\n",
    "  - 연산 비용이 높고 시간이 오래 걸림\n",
    "  - 대규모 문서 집합에 직접 적용하기 어려움\n",
    "\n",
    "## 실제 사용\n",
    "\n",
    "- 일반적으로 초기 검색에서 상위 k개의 문서에 대해서만 reranking 수행\n",
    "- Bi-encoder로 빠르게 후보를 추출한 후, Cross encoder로 정확도를 높이는 방식으로 활용\n",
    "\n",
    "## 구현\n",
    "\n",
    "- Hugging Face의 cross encoder 모델 또는 BAAI/bge-reranker와 같은 모델 사용\n",
    "- LangChain 등의 프레임워크에서 CrossEncoderReranker 컴포넌트를 통해 쉽게 통합 가능\n",
    "\n",
    "## Reranker의 주요 장점\n",
    "\n",
    "1. 더 정확한 유사도 측정\n",
    "2. 심층적인 의미론적 유사성 탐색\n",
    "3. 검색 결과 개선\n",
    "4. RAG 시스템 성능 향상\n",
    "5. 유연한 통합\n",
    "6. 다양한 사전 학습 모델 선택 가능\n",
    "\n",
    "## Reranker 사용 시 문서 수 설정\n",
    "\n",
    "- 일반적으로 상위 5~10개 문서에 대해 reranking 수행\n",
    "- 최적의 문서 수는 실험과 평가를 통해 결정 필요\n",
    "\n",
    "## Reranker 사용시 Trade-offs\n",
    "\n",
    "1. 정확도 vs 처리 시간\n",
    "2. 성능 향상 vs 계산 비용\n",
    "3. 검색 속도 vs 관련성 정확도\n",
    "4. 시스템 요구사항 충족\n",
    "5. 데이터셋 특성 고려"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "62f6a69f",
   "metadata": {},
   "source": [
    "간단한 예시를 통해 Cross Encoder Reranker의 구현 방법을 설명하겠습니다."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "0e17956b",
   "metadata": {},
   "outputs": [],
   "source": [
    "# 문서 출력 도우미 함수\n",
    "def pretty_print_docs(docs):\n",
    "    print(\n",
    "        f\"\\n{'-' * 100}\\n\".join(\n",
    "            [f\"Document {i+1}:\\n\\n\" + d.page_content for i, d in enumerate(docs)]\n",
    "        )\n",
    "    )"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "a3b1db61",
   "metadata": {},
   "outputs": [],
   "source": [
    "from langchain_community.document_loaders import TextLoader\n",
    "from langchain_community.vectorstores import FAISS\n",
    "from langchain_huggingface import HuggingFaceEmbeddings\n",
    "from langchain_text_splitters import RecursiveCharacterTextSplitter\n",
    "\n",
    "# 문서 로드\n",
    "documents = TextLoader(\"./data/appendix-keywords.txt\").load()\n",
    "\n",
    "# 텍스트 분할기 설정\n",
    "text_splitter = RecursiveCharacterTextSplitter(chunk_size=500, chunk_overlap=100)\n",
    "\n",
    "# 문서 분할\n",
    "texts = text_splitter.split_documents(documents)\n",
    "\n",
    "# 임베딩 모델 설정\n",
    "embeddingsModel = HuggingFaceEmbeddings(\n",
    "    model_name=\"sentence-transformers/msmarco-distilbert-dot-v5\"\n",
    ")\n",
    "\n",
    "# 문서로부터 FAISS 인덱스 생성 및 검색기 설정\n",
    "retriever = FAISS.from_documents(texts, embeddingsModel).as_retriever(\n",
    "    search_kwargs={\"k\": 10}\n",
    ")\n",
    "\n",
    "# 질의 설정\n",
    "query = \"Word2Vec 에 대해서 알려줄래?\"\n",
    "\n",
    "# 질의 수행 및 결과 문서 반환\n",
    "docs = retriever.invoke(query)\n",
    "\n",
    "# 결과 문서 출력\n",
    "pretty_print_docs(docs)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "74468bcf",
   "metadata": {},
   "source": [
    "이제 기본 `retriever`를 `ContextualCompressionRetriever`로 감싸보겠습니다. `CrossEncoderReranker`는 `HuggingFaceCrossEncoder`를 사용하여 반환된 결과를 재정렬합니다."
   ]
  },
  {
   "cell_type": "markdown",
   "id": "eef9113d",
   "metadata": {},
   "source": [
    "- 다국어 지원 BGE Reranker: [bge-reranker-v2-m3](https://huggingface.co/BAAI/bge-reranker-v2-m3)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "7951de8f",
   "metadata": {},
   "outputs": [],
   "source": [
    "from langchain.retrievers import ContextualCompressionRetriever\n",
    "from langchain.retrievers.document_compressors import CrossEncoderReranker\n",
    "from langchain_community.cross_encoders import HuggingFaceCrossEncoder\n",
    "\n",
    "# 모델 초기화\n",
    "model = HuggingFaceCrossEncoder(model_name=\"BAAI/bge-reranker-v2-m3\")\n",
    "\n",
    "# 상위 3개의 문서 선택\n",
    "compressor = CrossEncoderReranker(model=model, top_n=3)\n",
    "\n",
    "# 문서 압축 검색기 초기화\n",
    "compression_retriever = ContextualCompressionRetriever(\n",
    "    base_compressor=compressor, base_retriever=retriever\n",
    ")\n",
    "\n",
    "# 압축된 문서 검색\n",
    "compressed_docs = compression_retriever.invoke(\"Word2Vec 에 대해서 알려줄래?\")\n",
    "\n",
    "# 문서 출력\n",
    "pretty_print_docs(compressed_docs)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "f2238413",
   "metadata": {},
   "outputs": [],
   "source": [
    "compressed_docs"
   ]
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "langchain-kr-lwwSZlnu-py3.11",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.11.9"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 5
}
